<?php

namespace pmvc\util;

use pmvc\aop\ProxyUtil;
use pmvc\cache\Cache;

use Exception;
use ReflectionClass;
use ReflectionProperty;
use ReflectionMethod;

abstract class ReflectionUtil {

	/**
	 * Checks to see if the given $className is a
	 * $classIsA (if it can be cast to the given type).
	 * @param string $className
	 * @param string $classIsA
	 */
	public static function classIsA($className, $classIsA) {

		// fast return if possible
		if ($className==$classIsA) {
			return true;
		}

		// load the classes
		$clazz = new ReflectionClass($className);
		$clazzIsA = new ReflectionClass($classIsA);

		// check
		return ($clazzIsA->isInterface()) 
			? $clazz->implementsInterface($classIsA)
			: $clazz->isSubclassOf($classIsA);
	}
	
	/**
	 * Checks to see if the given object is a $class.
	 * (if it can be cast to the given type).
	 * @param mixed $obj
	 * @param string $class
	 */
	public static function isInstance($obj, $class) {
		if (!is_object($obj)) {
			return false;
		} else if ($obj instanceof $class || is_a($obj, $class)) {
			return true;
		} else if ($class!='pmvc\aop\Proxy' && ProxyUtil::isProxy($obj)) {
			return self::isInstance(ProxyUtil::getProxiedObject($obj), $class);
		}
		return false;
	}

	/**
	 * Sets a property on the given object.  First it trys
	 * accessor methods (setXxx and getXxx) and then it tries
	 * public properties.
	 * @param object $object
	 * @param string $name
	 * @param mixed $value
	 * @param bool $add
	 * @param ReflectionClass $refClass
	 * @throws Exception
	 */
	public static function setProperty(&$object, $name, $value, $add=true, ReflectionClass $refClass=null) {
		// get reflection class
		$refClass = isset($refClass) ? $refClass : new ReflectionClass($object);

		// try the setter
		if ($refClass->hasMethod("set".$name)) {
			$method = $refClass->getMethod("set".$name);
			$method->setAccessible(true);
			$method->invoke($object, $value);
			return;

		// try the property
		} else if ($refClass->hasProperty($name)) {
			$prop = $refClass->getProperty($name);
			$prop->setAccessible(true);
			$prop->setValue($object, $value);
			return;

		// add it
		} else if ($add===true) {
			$object->$name = $value;
			return;
		}

		// bad shit
		throw new Exception(
			"No property or mutator method for ".$name
			." found on type: ".$refClass->getName());
	}

	/**
	 * Gets a property on the given object.  First it trys
	 * accessor methods (setXxx and getXxx) and then it tries
	 * public properties.
	 * @param object $object
	 * @param string $name
	 * @param ReflectionClass $refClass
	 * @return the value
	 * @throws Exception
	 */
	public static function getProperty(&$object, $name, $strict=false, ReflectionClass $refClass=null) {
		// get reflection class
		$refClass = isset($refClass) ? $refClass : new ReflectionClass($object);

		// try the setter
		if ($refClass->hasMethod("get".$name)) {
			$method = $refClass->getMethod("get".$name);
			$method->setAccessible(true);
			return $method->invoke($object);

		// try the property
		} else if ($refClass->hasProperty($name)) {
			$prop = $refClass->getProperty($name);
			$prop->setAccessible(true);
			return $prop->getValue($object);

		// not strict
		} else if ($strict===false) {
			return null;
		}

		// bad shit
		throw new Exception(
			"No property or accessor method for ".$name
			." found on type: ".$refClass->getName());
	}

	/**
	 * Gets a list of properties for the given object.
	 * 
	 * @param mixed $object
	 * @param ReflectionClass $refClass
	 * @param bool $publicOnly
	 */
	public static function getProperties(&$object, ReflectionClass $refClass=null, $publicOnly=true) {
		// get reflection class
		$refClass = isset($refClass) ? $refClass : new ReflectionClass($object);

		// return value
		$ret = Array();

		// get the properties
		$filter = $publicOnly ? ReflectionProperty::IS_PUBLIC : null;
		foreach ($refClass->getProperties($filter) as $property) {
			$ret []= $property->getName();
		}

		// get the methods
		$filter = $publicOnly ? ReflectionMethod::IS_PUBLIC : null;
		foreach ($refClass->getMethods($filter) as $method) {
			$name = $method->getName();
			if (!StringUtil::startsWith($name, "get")) {
				continue;
			} else {
				$name = substr($name, 3);
			}
			if ($refClass->hasMethod("set".$name)) {
				$ret []= strtolower(substr($name, 0, 1)).substr($name, 1);
			}
		}

		// return it
		return $ret;
	}
}

?>